package com.googlecode.yoohoo.io.socket;

import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

import com.googlecode.yoohoo.io.socket.stream.IStreamFeaturesManager;
import com.googlecode.yoohoo.io.socket.stream.NegotiationPhase;
import com.googlecode.yoohoo.io.socket.stream.StreamFeatureContribution;
import com.googlecode.yoohoo.io.socket.stream.StreamFeaturesManager;
import com.googlecode.yoohoo.osgi.AbstractBundleActivator;
import com.googlecode.yoohoo.osgi.IBundleContextAware;
import com.googlecode.yoohoo.utils.AppUtils;
import com.googlecode.yoohoo.utils.Constants;
import com.googlecode.yoohoo.xmppcore.io.IConnector;
import com.googlecode.yoohoo.xmppcore.parsing.ParserContribution;
import com.googlecode.yoohoo.xmppcore.parsing.ParsersTracker;
import com.googlecode.yoohoo.xmppcore.protocol.IFeature;
import com.googlecode.yoohoo.xmppcore.protocol.parsing.IXmppParsingFactory;
import com.googlecode.yoohoo.xmppcore.protocol.parsing.PredefinedXmppParsingFactory;
import com.googlecode.yoohoo.xmppcore.protocol.parsing.ProtocolRelation;
import com.googlecode.yoohoo.xmppcore.protocol.stanza.Iq;
import com.googlecode.yoohoo.xmppcore.protocol.stream.Bind;
import com.googlecode.yoohoo.xmppcore.protocol.stream.BindParserFactory;
import com.googlecode.yoohoo.xmppcore.protocol.stream.Session;
import com.googlecode.yoohoo.xmppcore.protocol.stream.SessionParserFactory;
import com.googlecode.yoohoo.xmppcore.stanza.IStanzasHandler;
import com.googlecode.yoohoo.xmppcore.stanza.StanzaHandlerTracker;
import com.googlecode.yoohoo.xmppcore.stanza.StanzasHandler;
import com.googlecode.yoohoo.xmppcore.stream.INegotiationContext;
import com.googlecode.yoohoo.xmppcore.stream.features.Mechanisms;
import com.googlecode.yoohoo.xmppcore.stream.features.StartTls;

public class Activator extends AbstractBundleActivator {

	private static final Log log = LogFactory.getLog(Activator.class);
	
	private ServiceRegistration connectorRegistration;
	private ServiceRegistration featuresManagerRegistration;
	private Map<IFeature, NegotiationPhase> features;
	private IStreamFeaturesManager featuresManager;
	private ServiceTracker featureContributionTracker;
	
	private ServiceRegistration afterInitialStreamStanzaHandlerRegistration;
	private ServiceRegistration afterTlsStanzaHandlerRegistration;
	private StanzaHandlerTracker<INegotiationContext> afterInitialStreamStanzasHandlerTracker;
	private StanzaHandlerTracker<INegotiationContext> afterTlsStanzasHandlerTracker;
	
	private ServiceRegistration parsingFactoryRegistration;
	private ParsersTracker parsersTracker;
	
	@Override
	public void doStart(BundleContext context) throws Exception {
		if (!configManager.getBoolean("disable.connector")) {
			registerParsingFactory(context);
			registerFeatureContributions(context);
			registerStanzasHandlers(context);
			registerConnector(context);
		}
	}

	@SuppressWarnings("unchecked")
	private void registerStanzasHandlers(BundleContext context) {
		Object[] rets = registerStanzasHandler(context, NegotiationPhase.AFTER_INITIAL_STREAM);
		afterInitialStreamStanzaHandlerRegistration = (ServiceRegistration)rets[0];
		afterInitialStreamStanzasHandlerTracker = (StanzaHandlerTracker<INegotiationContext>)rets[1];
		rets = registerStanzasHandler(context, NegotiationPhase.AFTER_TLS);
		afterTlsStanzaHandlerRegistration = (ServiceRegistration)rets[0];
		afterTlsStanzasHandlerTracker = (StanzaHandlerTracker<INegotiationContext>)rets[1];
	}

	private void registerParsingFactory(BundleContext context) {
		IXmppParsingFactory parsingFactory = new PredefinedXmppParsingFactory();
		Dictionary<Object, Object> properties = new Hashtable<Object, Object>();
		properties.put(Constants.KEY_SERVICE_INSTANCE_NAME, Constants.CONSTANT_XMPP_STREAM);
		parsingFactoryRegistration = context.registerService(IXmppParsingFactory.class.getName(),
				parsingFactory, properties);
		
		parsersTracker = new ParsersTracker(context, parsingFactory, Constants.CONSTANT_XMPP_STREAM);
		parsersTracker.open();
		
		registerPredefinedParsers(context);
	}

	private void registerPredefinedParsers(BundleContext context) {
		registerBindParser(context);
		registerSessionParser(context);
	}

	private void registerBindParser(BundleContext context) {
		BindParserFactory bindParserFactory = new BindParserFactory();
		ProtocolRelation bindRelation = new ProtocolRelation(Iq.PROTOCOL_KEY, Bind.PROTOCOL_KEY);
		ParserContribution bindParserContribution = new ParserContribution(bindParserFactory, bindRelation);
		Dictionary<Object, Object> properties = new Hashtable<Object, Object>();
		properties.put(Constants.KEY_SERVICE_INSTANCE_NAME, Constants.CONSTANT_XMPP_STREAM);
		
		context.registerService(ParserContribution.class.getName(), bindParserContribution, properties);
	}
	
	private void registerSessionParser(BundleContext context) {
		SessionParserFactory sessionParserFactory = new SessionParserFactory();
		ProtocolRelation sessionRelation = new ProtocolRelation(Iq.PROTOCOL_KEY, Session.PROTOCOL_KEY);
		ParserContribution sessionParserContribution = new ParserContribution(sessionParserFactory, sessionRelation);
		Dictionary<Object, Object> properties = new Hashtable<Object, Object>();
		properties.put(Constants.KEY_SERVICE_INSTANCE_NAME, Constants.CONSTANT_XMPP_STREAM);
		
		context.registerService(ParserContribution.class.getName(), sessionParserContribution, properties);
	}

	private void registerConnector(BundleContext context) {
		IConnector connector = new Connector();
		if (connector instanceof IBundleContextAware) {
			((IBundleContextAware)connector).setBundleContext(context);
		}
		connectorRegistration = context.registerService(IConnector.class.getName(), connector, null);
	}

	private void registerFeatureContributions(final BundleContext context) {
		featuresManager = new StreamFeaturesManager();
		featuresManagerRegistration = context.registerService(IStreamFeaturesManager.class.getName(),
				featuresManager, null);
		
		features = new HashMap<IFeature, NegotiationPhase>();
		features.put(new StartTls(isTlsRequired(context)), NegotiationPhase.INITIAL_STREAM);
		features.put(new Mechanisms(), NegotiationPhase.INITIAL_STREAM);
		features.put(new Mechanisms(), NegotiationPhase.TLS);
		
		Iterator<Map.Entry<IFeature, NegotiationPhase>> featuresIter =
			features.entrySet().iterator();
		while (featuresIter.hasNext()) {
			Map.Entry<IFeature, NegotiationPhase> featureEntry = featuresIter.next();
			featuresManager.addFeature(featureEntry.getKey(), featureEntry.getValue());
		}
		
		featureContributionTracker = new ServiceTracker(context, StreamFeatureContribution.class.getName(),
				new ServiceTrackerCustomizer() {
			
			@Override
			public void removedService(ServiceReference reference, Object service) {
				if (service == null)
					return;
				
				StreamFeatureContribution contribution = (StreamFeatureContribution)service;
				featuresManager.removeFeature(contribution.getFeature(), contribution.getPhase());
				
				if (log.isInfoEnabled()) {
					log.info(String.format("Stream feature '%s' removed.", contribution.getFeature().toXml()));
				}
			}
			
			@Override
			public void modifiedService(ServiceReference reference, Object service) {
			}
			
			@Override
			public Object addingService(ServiceReference reference) {
				StreamFeatureContribution contribution = (StreamFeatureContribution)context.getService(reference);
				if (contribution != null) {
					featuresManager.addFeature(contribution.getFeature(), contribution.getPhase());
				}
				
				if (log.isInfoEnabled()) {
					log.info(String.format("Stream feature '%s' added.", contribution.getFeature().toXml()));
				}
				
				return contribution;
			}
		});
		featureContributionTracker.open();
	}
	
	private Object[] registerStanzasHandler(BundleContext context, NegotiationPhase phase) {
		IStanzasHandler<INegotiationContext> stanzasHandler = new StanzasHandler<INegotiationContext>(context);
		Dictionary<Object, Object> properties = new Hashtable<Object, Object>();
		properties.put(IStanzasHandler.KEY_STANZAS_HANDLER_TYPE, phase.toString());
		ServiceRegistration registration = context.registerService(StanzasHandler.class.getName(),
				stanzasHandler, properties);
		StanzaHandlerTracker<INegotiationContext> tracker = new StanzaHandlerTracker<INegotiationContext>(context,
				stanzasHandler, phase.toString());
		tracker.open();
		
		return new Object[] {registration, tracker};
	}

	private boolean isTlsRequired(BundleContext bundleContext) {
		return AppUtils.getConfigManager(bundleContext).getBoolean(ConfigManager.KEY_TLS_REQUIRED);
	}

	@Override
	public void doStop(BundleContext context) throws Exception {
		if (!configManager.getBoolean("disable.connector")) {
			unregisterConnector();
			unregisterFeatureContributionStanzaHandlers();
			unregisterFeaturesManager();
			unregisterParsingFactory();
		}
	}

	private void unregisterFeatureContributionStanzaHandlers() {
		afterTlsStanzasHandlerTracker.close();
		afterTlsStanzaHandlerRegistration.unregister();
		
		afterInitialStreamStanzasHandlerTracker.close();
		afterInitialStreamStanzaHandlerRegistration.unregister();
	}

	private void unregisterParsingFactory() {
		parsersTracker.close();
		
		if (parsingFactoryRegistration != null) {
			parsingFactoryRegistration.unregister();
		}
	}

	private void unregisterFeaturesManager() {
		featureContributionTracker.close();
		
		Iterator<Map.Entry<IFeature, NegotiationPhase>> featuresIter =
			features.entrySet().iterator();
		while (featuresIter.hasNext()) {
			Map.Entry<IFeature, NegotiationPhase> featureEntry = featuresIter.next();
			featuresManager.removeFeature(featureEntry.getKey(), featureEntry.getValue());
		}
		
		if (featuresManagerRegistration != null) {
			featuresManagerRegistration.unregister();
		}
	}

	private void unregisterConnector() {
		if (connectorRegistration != null) {
			connectorRegistration.unregister();
		}
	}

}
